package ws

import (
	"bytes"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

var upgrader = Upgrader{
	ReadBufferSize: 1024,
	WriteBufferSize: 1024,
}

var serial int64 = 0

type Config struct {
	Host string
	Port int
	Path string
	Token string
	WriteWait int
	PongWait int
	Origin string
	Version int
}

func (this *Config) Set(key string, value string) {
	switch strings.ToLower(key) {
	case "host":
		this.Host = value
	case "port":
		this.Port, _ = strconv.Atoi(value)
	case "path":
		this.Path = value
	case "token":
		this.Token = value
	case "writewait":
		this.WriteWait, _ = strconv.Atoi(value)
	case "pongwait":
		this.PongWait, _ = strconv.Atoi(value)
	case "origin":
		this.Origin = value
	case "version":
		this.Version, _ = strconv.Atoi(value)
	}
}

type EventFunc func(conn *Conn, message string)

type Request struct {
	conn *Conn
	handle *http.Request

	Send chan []byte

	WriteWait time.Duration
	PongWait time.Duration
	PingPeriod time.Duration

	FId int64
	UId string
	RoomId int64
	Server *Server
}

func (this *Request) readPump() {
	defer func() {
		this.Server.UnRegister <- this
		this.conn.Close()
	}()

	this.conn.SetReadDeadline(time.Now().Add(this.PongWait))
	this.conn.SetPongHandler(func(string) error { this.conn.SetReadDeadline(time.Now().Add(this.PongWait)); return nil })
	for {
		_, message, err := this.conn.ReadMessage()
		if err != nil {
			if IsUnexpectedCloseError(err, CloseGoingAway, CloseAbnormalClosure) {
				log.Printf("error: %v", err)
			}

			break
		}
		message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		this.Server.Message(this, string(message))
	}
}

func (this *Request) writePump() {
	ticker := time.NewTicker(this.PingPeriod)
	defer func() {
		ticker.Stop()
		this.conn.Close()
	}()
	for {
		select {
		case message, ok := <-this.Send:
			this.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				this.conn.WriteMessage(CloseMessage, []byte{})
				return
			}

			w, err := this.conn.NextWriter(TextMessage)
			if err != nil {
				return
			}
			w.Write(message)
			n := len(this.Send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				w.Write(<-this.Send)
			}

			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			this.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := this.conn.WriteMessage(PingMessage, nil); err != nil {
				return
			}
		}
	}
}

func (this *Request) Header(key string) string {
	return this.handle.Header.Get(key)
}

func (this *Request) Query(key string) string {
	temp := this.handle.URL.Query()[key]
	return strings.Join(temp, ",")
}

func (this *Request) Close() error {
	return this.conn.Close()
}

type Server struct {
	Config *Config
	Events map[string]interface{}
	Requests map[*Request]bool

	Broadcast chan []byte
	Register chan *Request
	UnRegister chan *Request
}

func (this *Server) ServeHTTP(rep http.ResponseWriter, req *http.Request) {
	conn, err := upgrader.Upgrade(rep, req, nil)
	if err != nil {
		return
	}

	atomic.AddInt64(&serial, 1)

	request := &Request{
		conn: conn,
		handle: req,

		FId: serial,
		RoomId: 0,
		Server: this,
		Send: make(chan []byte, 256),
	}

	request.Server.Register <- request

	request.WriteWait = time.Duration(this.Config.WriteWait) * time.Second
	request.PongWait = time.Duration(this.Config.PongWait) * time.Second
	request.PingPeriod = (request.PongWait * 9) / 10

	go request.readPump()
	go request.writePump()
}

func (this *Server) Run() {
	if startFunc, ok := this.Events["start"]; ok {
		switch startFunc.(type) {
		case func(serv *Server):
			go startFunc.(func(serv *Server))(this)
		}
	}

	for {
		select {
		case client := <-this.Register:
			this.Requests[client] = true

			if connectFunc, ok := this.Events["connect"]; ok {
				switch connectFunc.(type) {
				case func(req *Request):
					go connectFunc.(func(req *Request))(client)
				}
			}
		case client := <-this.UnRegister:
			if _, ok := this.Requests[client]; ok {
				delete(this.Requests, client)
				close(client.Send)

				if closeFunc, ok := this.Events["close"]; ok {
					switch closeFunc.(type) {
					case func(req *Request):
						go closeFunc.(func(req *Request))(client)
					}
				}
			}
		case message := <-this.Broadcast:
			for client := range this.Requests {
				select {
				case client.Send <- message:
				default:
					close(client.Send)
					delete(this.Requests, client)
				}
			}
		}
	}
}

func (this *Server) HandlePUSH(rep http.ResponseWriter, req *http.Request) {
	conn := &Conn{
		handle: req,
		Server: this,
	}

	if pushFunc, ok := this.Events["push"]; ok {
		switch pushFunc.(type) {
		case func(c *Conn):
			go pushFunc.(func(c *Conn))(conn)
		}
	}
}

func (this *Server) Message(client *Request, message string) {
	if _, ok := this.Requests[client]; ok {
		if messageFunc, ok := this.Events["message"]; ok {
			switch messageFunc.(type) {
			case func(req *Request, message string):
				go messageFunc.(func(req *Request, message string))(client, message)
			}
		}
	}
}